#pragma once
#include <format>
#include <string>
using namespace std;

/**
 * Gregorian历法实施年份
 */
#define GREGORIAN_FIRST_YEAR 1582
/**
 * Gregorian历法实施月份
 */
#define GREGORIAN_FIRST_MONTH 10
/**
 * Gregorian历法实施日期
 */
#define GREGORIAN_FIRST_DATE 15
/**
 * Gregorian历法实施日期数,这里不是标准的日期计算;
 */
#define GREGORIAN_FIRST_DAY (GREGORIAN_FIRST_DATE + 31 * (GREGORIAN_FIRST_MONTH + GREGORIAN_FIRST_YEAR * 12))

/**
 * Julian历法废弃年份
 */
#define JULIAN_LAST_YEAR 1582
/**
 * Julian历法废弃月份
 */
#define JULIAN_LAST_MONTH 10
/**
 * Julian历法废弃日期
 */
#define JULIAN_LAST_DATE 4
/**
 * julian历法废弃日期数,这里不是标准的日期计算;
 */
#define JULIAN_LAST_DAY (JULIAN_LAST_DATE + 31 * (JULIAN_LAST_MONTH + JULIAN_LAST_YEAR * 12))

/**
 * Gregorian历TT1582年10月15日中午12点的儒略日;
 */
#define JULIAN_GREGORIAN_BOUNDARY 2299161

/**
 * Gregorian历TT2000年1月1日中午12点的儒略日;
 */
// #define J2000 2451545.0

/**
 * 1000年的日数
 */
#define DAYS_OF_1000_YEARS 365250.0

/**
 * 100年的日数
 */
#define DAYS_OF_CENTURY 36525.0

// https://github.com/oyyq99999/ChineseLunarCalendar/blob/master/src/oyyq/calendar/util/CalendarUtil.java

struct JDateTime
{
    JDateTime(const JDateTime &jdt) { *this = jdt; }
    /*
     *
     * 默认日期时间: [ 2000-01-01 12:00:00 ];
     *
     */
    JDateTime() : dateYear(2000), dateMonth(1), dateDay(1), dateHour(12), dateMinute(0), dateSecond(0), dateJulianDay(2451545.0) {};
    /**
     * 根据 日期时间 计算，以TT当天中午12点为准， 算法摘自<a href= "http://en.wikipedia.org/wiki/Julian_day";
     *
     * 注意：
     *
     *  1. 1582年10月4日及以前按照Julian历法，1582年10月15日及以后按照Gregorian历法，中间部分以 1582年10月15日计算;
     *
     *  2. 年份=0 自动调整为 年份=-1;
     *
     *  3. 函数中为做月,日,时,分,秒,数值取值范围判断;
     *
     * @param year
     *            年份[非零数值];
     * @param month
     *            月份[1-12] (默认 1);
     * @param day
     *            日期[1-31] (默认 1);
     * @param hour
     *            小时[0-23] (默认 0);
     * @param minute
     *            分钟[0-59] (默认 0);
     * @param second
     *            秒数[0-59] (默认 0);
     * @return 无返回
     */
    JDateTime(int year, unsigned short month = 1, unsigned short day = 1, unsigned short hour = 0, unsigned short minute = 0, unsigned short second = 0)
    {
        this->dateYear      = year;
        this->dateMonth     = month;
        this->dateDay       = day;
        this->dateHour      = hour;
        this->dateMinute    = minute;
        this->dateSecond    = second;

        //  计算;
        this->dateJulianDay = this->of(year, month, day, hour, minute, second);
    }
    /**
     * 传递jd值，以TT当天中午12点为准， 算法摘自<a href= "http://en.wikipedia.org/wiki/Julian_day";
     *
     * @param hour
     *
     * @return 无返回
     */
    JDateTime(double jd) { *this = this->of(jd); }

public:
    /**
     * 闰年判断
     * 1582 年(含)以前按照Julian历法，1582年以后按照Gregorian历法。
     *
     * @param y
     *            年份
     * @return 闰年返回true，平年返回false
     */
    static bool isLeapYear(int y)
    {
        if (y <= JULIAN_LAST_YEAR)
        {
            return ((y & 3) == 0);
        }
        return ((y & 3) == 0 && y % 100 != 0 || y % 400 == 0);
    }

    /**
     * 计算儒略日数，以TT当天中午12点为准， 算法摘自<a href= "http://en.wikipedia.org/wiki/Julian_day";
     *
     * 注意：
     *
     *  1. 1582年10月4日及以前按照Julian历法，1582年10月15日及以后按照Gregorian历法，中间部分以 1582年10月15日计算;
     *
     *  2. 年份=0 自动调整为 年份=-1;
     *
     *  3. 函数中为做月,日,时,分,秒,数值取值范围判断;
     *
     * @param year
     *            年份[非零数值];
     * @param month
     *            月份[1-12] (默认 1);
     * @param day
     *            日期[1-31] (默认 1);
     * @param hour
     *            小时[0-23] (默认 0);
     * @param minute
     *            分钟[0-59] (默认 0);
     * @param second
     *            秒数[0-59] (默认 0);
     * @return 返回相应历法的儒略日数;
     */
    static double of(int year, unsigned short month = 1, unsigned short day = 1, unsigned short hour = 0, unsigned short minute = 0, unsigned short second = 0);

    /**
     * 由儒略日计算对应的日历日期时间，算法参考<i>Jean Meeus</i>的<i>Astronomical Formulae for Calculators</i>
     *
     * @param jd
     *            儒略日;
     * @param tz
     *            要使用的时区 (默认 0);
     * @return 对应的Julian历TT日期时间的一个Calendar对象，时区为 tz。;
     */
    static JDateTime of(double jd, int tz = 0);

    /**
     * @brief  星期几。 算法摘自<a href= "http://en.wikipedia.org/wiki/Zeller%27s_congruence">英文维基百科<i>Zeller's congruence</i>词条</a>。
     *   [nnweek]
     * @param year
     *            年份[非零数值];
     * @param month
     *            月份[1-12] (默认 1);
     * @param day
     *            日期[1-31] (默认 1);
     * @return 星期几的数字表示，1-6表示星期一到星期六，0表示星期日
     */
    static unsigned short getWeekday(int year, int month, int day);

public:
    /**
     *   年份
     * @return 年份 [-47~-1,1-.....] 返回0 为错误;
     */
    const int getYear() const { return this->dateYear; };
    /**
     *   月份。
     * @return 日期-号数[1 - 11];
     */
    const unsigned short getMonth() const { return this->dateMonth; };
    /**
     *   日期数。
     * @return 日期数[1 - 31];
     */
    const unsigned short getDay() const { return this->dateDay; };
    /**
     *   小时数。
     * @return 小时[0 - 23];
     */
    const unsigned short getHour() const { return this->dateHour; };
    /**
     *   分钟数。
     * @return 分钟[0 - 59];
     */
    const unsigned short getMinute() const { return this->dateMinute; };

    /**
     *   秒数。
     * @return 秒[0 - 59];
     */
    const unsigned short getSecond() const { return this->dateSecond; };

    /**
     *   星期几。
     *
     * @return 星期几的数字表示，1-6表示星期一到星期六，0表示星期日
     */
    const unsigned short getWeekDay() const;

    /**
     *   儒略日数。
     *
     * @return 儒略日数;
     */
    const double getNumberForJulianDay() const { return this->dateJulianDay; };

    /**
    * @brief 获取指定年月的月天数;
    * 
    * @param year 
    *         年;
    * @param int 
    *         月;
    * @return const int 
    */
    const static int getDaysOfMonth(int year, unsigned int month) noexcept;
    /**
     * 判断是否闰年;
     * 1582 年(含)以前按照Julian历法，1582年以后按照Gregorian历法。
     *
     * @return  闰年返回true，平年返回false
     */
    const bool isLeapYear() const { return this->isLeapYear(this->dateYear); }

    /**
     *   设置日期部分;
     * 注意：
     *     本函数会验证 (年,月,日,时,分,秒)是否符合标准,并自动设置为 最大值/最小值;
     *
     * @param year
     *            年份[非零数值];
     * @param month
     *            月份[1-12] (默认 1);
     * @param day
     *            日期[1-31] (默认 1);
     */
    void setDate(int year, unsigned short month = 1, unsigned short day = 1);

    /**
     *   设置时间部分;
     * 注意：
     *     本函数会验证 (年,月,日,时,分,秒)是否符合标准,并自动设置为 最大值/最小值;
     *
     * @param hour
     *            小时[0-23];
     * @param minute
     *            分钟[0-59] (默认 0);
     * @param second
     *            秒数[0-59] (默认 0);
     * @return 星期几的数字表示，1-6表示星期一到星期六，0表示星期日
     */
    void setTime(unsigned short hour, unsigned short minute = 0, unsigned short second = 0)
    {
        this->dateHour      = hour;
        this->dateMinute    = minute;
        this->dateSecond    = second;

        this->dateJulianDay = this->of(this->dateYear, this->dateMonth, this->dateDay, hour, minute, second);
    }
    /**
     *   设置儒略日数。
     * 注意：
     *     本函数自动更新(年,月,日,时,分,秒);
     */
    void setJulianDateTime(double jdt) { *this = this->of(jdt); }

    /**
     *  字符串 日期时间格式
     *
     * @return 日期时间格式化字符串;
     */
    std::string ToString()
    {
        auto str = std::format(
            "{:<-d}-{:02d}-{:02d}T{:02d}:{:02d}:{:02d}", this->dateYear, this->dateMonth, this->dateDay, this->dateHour, this->dateMinute, this->dateSecond);
        return str;
    }
    /**
     *  字符串 时间格式
     *
     * @return 时间格式化字符串;
     */
    std::string ToTimeString()
    {
        auto str = std::format("{:02d}:{:02d}:{:02d}", this->dateHour, this->dateMinute, this->dateSecond);
        return str;
    }
    /**
     *  字符串 日期格式
     *
     * @return 日期格式化字符串;
     */
    std::string ToDateString()
    {
        auto str = std::format("{:<-d}-{:02d}-{:02d}", this->dateYear, this->dateMonth, this->dateDay);
        return str;
    }
    /**
     * @brief 验证60干支;
     *
     * @param hs 天干[0-9];
     * @param tb 地支[0-11];
     * @return true  该组合正确;
     * @return false 该组合错误;
     */
    bool verifySexagesimalCycle(int hs, int tb) { return ((hs % 2) == (tb % 2)); }

    /**
     * @brief 根据 天干序号和地支序号反推60天干地支循环序号;
     *
     * @param hs 天干[0-9];
     * @param tb 地支[0-11];
     * @return int 60天干地支循环序号[0-59], [ -1 = 错误];
     */
    int getSexagesimalCycleIndex(int hs, int tb)
    {
        if (!verifySexagesimalCycle(hs, tb))
            return -1;
        if (hs >= tb)
            tb = tb + 12;
        return (6 - (tb - hs) / 2) * 10 + hs;
        ;
    }

public:
    /**
     * @brief 字符串 时间格式
     *
     * @param jd
     *         儒略日数;
     * @return std::string
     */
    static std::string ToTimeString(double jd) noexcept;
    /**
     * @brief 字符串 日期时间格式
     *
     * @param jd
     *         儒略日数;
     * @return std::string
     */
    static std::string ToString(double jd)
    {
        JDateTime jdt(jd);
        return jdt.ToString();
    }

    // https://zhuanlan.zhihu.com/p/36168407;

    // 重载部分;
public:
    // this = ohter;
    JDateTime &operator=(const JDateTime &other)
    {
        if (*this != other)
        {
            this->dateYear      = other.dateYear;
            this->dateMonth     = other.dateMonth;
            this->dateDay       = other.dateDay;
            this->dateHour      = other.dateHour;
            this->dateMinute    = other.dateMinute;
            this->dateSecond    = other.dateSecond;

            this->dateJulianDay = other.dateJulianDay;
        }
        return *this;
    }

    // JDateTime2 =  JDateTime1 + days;
    JDateTime &operator+(const int days)
    {
        this->dateJulianDay += days;

        *this = of(this->dateJulianDay);
        return *this;
    }
    // JDateTime +=  days;
    JDateTime &operator+=(const double days)
    {
        this->dateJulianDay += days;

        *this = of(this->dateJulianDay);
        return *this;
    }
    // JDateTime +=  days;
    JDateTime &operator+=(const int days)
    {
        this->dateJulianDay += (double)days;

        *this = of(this->dateJulianDay);
        return *this;
    }
    //++JDateTime;
    JDateTime &operator++()
    {
        this->dateJulianDay++;

        *this = of(this->dateJulianDay);
        return *this;
    }
    //--JDateTime;
    JDateTime &operator--()
    {
        this->dateJulianDay--;

        *this = of(this->dateJulianDay);
        return *this;
    }
    // JDateTime++;
    JDateTime &operator++(int)
    {
        this->dateJulianDay++;

        *this = of(this->dateJulianDay);
        return *this;
    }
    // JDateTime--;
    // JDateTime &operator--(int)
    // {
    //     JDateTime tmp(*this);
    //     tmp--;
    //     return tmp;
    // }
    // this == other;
    bool operator==(const JDateTime &other) { return (this->dateJulianDay == other.dateJulianDay); }
    // this != other;
    bool operator!=(const JDateTime &other) { return !this->operator==(other); }
    // this >= other;
    bool operator>=(const JDateTime &other) { return (this->dateJulianDay >= other.dateJulianDay); }
    // this <= other;
    bool operator<=(const JDateTime &other) { return (this->dateJulianDay <= other.dateJulianDay); }
    // this != other;
    bool operator>(const JDateTime &other) { return !this->operator<=(other); }
    // this != other;
    bool operator<(const JDateTime &other) { return !this->operator>=(other); }

private:
    // 日期时间型;
    // 年;
    signed int dateYear;
    // 月;
    unsigned short dateMonth;
    // 日;
    unsigned short dateDay;
    // 时;
    unsigned short dateHour;
    // 分;
    unsigned short dateMinute;
    // 秒;
    unsigned short dateSecond;
    // 毫秒 - 暂时不启用-貌似没什么意义;
    // unsigned short dateMillisecond;
private:
    // 儒略日数及时间(小数部分);
    double dateJulianDay;
};
